﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ExtendedKeystrokeEngine.cs" company="arvystate.net">
//   arvystate.net
// </copyright>
// <summary>
//   Engine processes gesture and sends mapped keystroke to either base or alternate mapping.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace PresentationAid.Lib.GestureEngines
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Windows.Input;

    using NLog;

    using PresentationAid.Lib.Enums;
    using PresentationAid.Lib.GestureEngines.GestureSettings;

    /// <summary>
    /// Engine processes gesture and sends mapped keystroke to either base or alternate mapping.
    /// </summary>
    public class ExtendedKeystrokeEngine : KeystrokeEngine
    {
        #region Static Fields

        /// <summary>
        /// The logger.
        /// </summary>
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        #endregion

        #region Fields

        /// <summary>
        /// The _alternate flag.
        /// </summary>
        private readonly Dictionary<GestureCommand, bool> alternateFlag;

        /// <summary>
        /// The _alternate key mapping.
        /// </summary>
        private readonly Dictionary<GestureCommand, Key> alternateKeyMapping;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ExtendedKeystrokeEngine"/> class.
        /// </summary>
        public ExtendedKeystrokeEngine()
        {
            Logger.Trace("Init: ExtendedKeystrokeEngine");

            this.alternateKeyMapping = new Dictionary<GestureCommand, Key>();
            this.alternateFlag = new Dictionary<GestureCommand, bool>();

            Logger.Trace("InitDetail: ExtendedKeystrokeEngine - CreateSettings");

            this.EngineSettings = new ExtendedKeystrokeSettings(this);
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Clears currently mapped alternate gesture command
        /// </summary>
        /// <param name="gesture">
        /// Gesture to clear alternate mapping for
        /// </param>
        public void ClearAlternateMapping(GestureCommand gesture)
        {
            if (this.alternateKeyMapping.ContainsKey(gesture))
            {
                Logger.Trace("Method: AlternateMapping - Gesture: {0}", gesture.ToString());

                this.alternateKeyMapping.Remove(gesture);
                this.alternateFlag.Remove(gesture);
            }
        }

        /// <summary>
        /// Gets alternate key mapping
        /// </summary>
        /// <param name="gesture">
        /// Gesture to return alternate mapping for
        /// </param>
        /// <returns>
        /// Alternate key mapped to gesture <see cref="Key"/>.
        /// </returns>
        public Key GetAlternateMapping(GestureCommand gesture)
        {
            return this.alternateKeyMapping.ContainsKey(gesture) ? this.alternateKeyMapping[gesture] : Key.None;
        }

        /// <summary>
        /// Returns mapping in string format
        /// </summary>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public override string GetMapping()
        {
            StringBuilder mapping = new StringBuilder();

            // Add control gesture
            mapping.Append(this.ControlGesture);
            mapping.Append(":TakeControl;");

            foreach (KeyValuePair<GestureCommand, Key> pair in this.KeyMapping)
            {
                if (pair.Key == this.ControlGesture)
                {
                    continue;
                }

                mapping.Append(pair.Key + ":" + pair.Value + ":");

                mapping.Append(
                    this.alternateKeyMapping.ContainsKey(pair.Key) ? this.alternateKeyMapping[pair.Key] : Key.None);

                mapping.Append(";");
            }

            // Remove last semicolon
            mapping.Remove(mapping.Length - 1, 1);

            Logger.Debug("Method: GetMapping - Return: {0}", mapping.ToString());

            return mapping.ToString();
        }

        /// <summary>
        /// Returns slideshow command mapped to gesture, based on PowerPoint keyboard shortcuts
        /// </summary>
        /// <param name="gesture">
        /// Gesture to get slideshow command for
        /// </param>
        /// <returns>
        /// SlideShowCommand mapped to gesture <see cref="SlideShowCommand"/>.
        /// </returns>
        public override SlideShowCommand GetSlideShowCommand(GestureCommand gesture)
        {
            if (this.IsAtAlternate(gesture))
            {
                switch (this.GetMapping(gesture))
                {
                    case Key.F5:
                        return SlideShowCommand.StartStopSlideshow;
                    case Key.Back:
                    case Key.Left:
                    case Key.Up:
                    case Key.PageUp:
                    case Key.P:
                        return SlideShowCommand.PreviousSlide;
                    case Key.Right:
                    case Key.Down:
                    case Key.Space:
                    case Key.PageDown:
                    case Key.N:
                    case Key.Enter:
                        return SlideShowCommand.NextSlide;
                }

                return SlideShowCommand.None;
            }

            return base.GetSlideShowCommand(gesture);
        }

        /// <summary>
        /// The is at alternate.
        /// </summary>
        /// <param name="command">
        /// The command.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public bool IsAtAlternate(GestureCommand command)
        {
            return this.alternateFlag.ContainsKey(command) && this.alternateFlag[command];
        }

        /// <summary>
        /// Method parses mapping as a string
        /// </summary>
        /// <param name="map">
        /// Mapping as a string
        /// </param>
        /// <returns>
        /// True on success <see cref="bool"/>.
        /// </returns>
        public override bool ParseMapping(string map = null)
        {
            if (!string.IsNullOrEmpty(map))
            {
                Logger.Debug("Method: ParseMapping - Map: {0}", map);

                try
                {
                    this.KeyMapping.Clear();

                    string[] mappings = map.Split(';');

                    foreach (string[] keyCommand in mappings.Select(command => command.Split(':')))
                    {
                        if (keyCommand[1].Trim() == "TakeControl")
                        {
                            this.ControlGesture = (GestureCommand)Enum.Parse(typeof(GestureCommand), keyCommand[0]);
                        }
                        else
                        {
                            this.SetMapping(
                                (GestureCommand)Enum.Parse(typeof(GestureCommand), keyCommand[0]), 
                                (Key)Enum.Parse(typeof(Key), keyCommand[1]));

                            // Alternate mapping
                            if (!string.IsNullOrEmpty(keyCommand[2]))
                            {
                                this.SetAlternateMapping(
                                    (GestureCommand)Enum.Parse(typeof(GestureCommand), keyCommand[0]), 
                                    (Key)Enum.Parse(typeof(Key), keyCommand[2]));
                            }
                        }
                    }

                    return true;
                }
                catch (Exception ex)
                {
                    Logger.Warn("Method: ParseMapping - Exception: {0}", ex.Message);
                }
            }

            this.SetDefaultMapping();

            return false;
        }

        /// <summary>
        /// Sets alternate mapping for key
        /// </summary>
        /// <param name="gesture">
        /// Gesture to set alternate mapping for
        /// </param>
        /// <param name="key">
        /// Alternate key
        /// </param>
        public void SetAlternateMapping(GestureCommand gesture, Key key)
        {
            Logger.Trace("Method: SetAlternateMapping - Gesture: {0}, Key: {1}", gesture.ToString(), key.ToString());

            if (key == Key.None)
            {
                this.ClearAlternateMapping(gesture);
            }

            if (this.alternateKeyMapping.ContainsKey(gesture))
            {
                this.alternateKeyMapping[gesture] = key;
            }
            else
            {
                this.alternateKeyMapping.Add(gesture, key);
                this.alternateFlag.Add(gesture, false);
            }
        }

        /// <summary>
        /// Sets default mapping
        /// </summary>
        public override void SetDefaultMapping()
        {
            Logger.Debug("Method: SetDefaultMapping");

            // Default mapping equals to kestroke mapping, except we have another command
            base.SetDefaultMapping();

            this.SetAlternateMapping(GestureCommand.Wave, Key.Escape);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The execute specific gesture.
        /// </summary>
        /// <param name="gesture">
        /// The gesture.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        protected override bool ExecuteSpecificGesture(GestureCommand gesture)
        {
            if (!this.alternateKeyMapping.ContainsKey(gesture))
            {
                return base.ExecuteSpecificGesture(gesture);
            }

            // Send alternate key if alternate flag is set
            Logger.Trace(
                "Method: ExecuteSpecificGesture: Gesture: {0}, AlternateFlag: {1}, IsInSlideshow", 
                gesture.ToString(), 
                this.alternateFlag[gesture], 
                this.IsInSlideshow);

            if (this.alternateFlag[gesture])
            {
                this.alternateFlag[gesture] = false;

                SendKeys.SendWait(this.KeystrokeToCommandString(this.alternateKeyMapping[gesture]));

                // Need to handle is in slideshow
                if (this.GetSlideShowCommand(gesture) == SlideShowCommand.StartStopSlideshow)
                {
                    this.IsInSlideshow = false;
                }

                return true;
            }

            if (this.GetSlideShowCommand(gesture) == SlideShowCommand.StartStopSlideshow)
            {
                this.IsInSlideshow = true;
            }

            this.alternateFlag[gesture] = true;

            return base.ExecuteSpecificGesture(gesture);
        }

        #endregion
    }
}